import _ from 'lodash'

export function clone (obj) {
  const s = encode(obj)
  if (s) {
    return decode(s)
  }
}

function replacer (key, val) {
  // remove properties whose key starts with '__'
  if (_.startsWith(key, '__')) {
    return undefined
  }
  // Error can't be JSON stringified, replace it with a plain object.
  if (val instanceof Error) {
    let error = {}
    Object.getOwnPropertyNames(val).forEach(function (k) {
      error[k] = val[k]
    })
    return error
  }
  return val
}

export function encode (obj) {
  return JSON.stringify(obj, replacer)
}

export function decode (raw) {
  return JSON.parse(raw)
}

export function isObject (obj) {
  const type = typeof obj
  return obj != null && (type === 'object' || type === 'function')
}

export function visit (obj, visitor, stack) {
  if (!isObject(obj)) {
    return
  }

  // TODO: Check for circular references and stop visiting.
  stack || (stack = [])

  const props = Object.keys(obj)
  for (let i = 0; i < props.length; i++) {
    const key = props[i]
    const val = obj[key]

    stack.push({ key, val })

    if (visitor(val, key, val) === false) {
      stack.pop()
      continue
    }

    // Recursively visit (susceptible to call stack limits).
    visit(val, visitor, stack)

    stack.pop()
  }
}

export function diffChange (source, snapshot, schema) {
  const differentProperties = {}

  for (const prop in source) {
    if (!schema.hasOwnProperty(prop)) continue
    if (isEqual(source[prop], snapshot[prop])) continue
    if (isNotNullObject(schema[prop])) {
      if (typeof source[prop] !== 'object' || typeof snapshot !== 'object') throw '不符合schema定义'
      if (source[prop] === null) differentProperties[prop] = null
      else differentProperties[prop] = diffChange(source[prop], snapshot[prop], schema[prop])
    } else {
      differentProperties[prop] = source[prop]
    }
  }

  return differentProperties
}

function isNotNullObject(value) {
  return typeof value === 'object' && value !== null
}

function isEqual (value1, value2) {
  if (typeof value1 !== 'object' || typeof value2 !== 'object') {
    return value1 === value2
  }
  if (value1 === null || value2 === null) return value1 === value2

  const keys1 = Object.keys(value1)
  const keys2 = Object.keys(value2)

  if (keys1.length !== keys2.length) {
    return false
  }

  for (const key of keys1) {
    if (!value2.hasOwnProperty(key) || !isEqual(value1[key], value2[key])) {
      return false
    }
  }

  return true
}